# Perforce commands wrapped in a module.
#
# Luc Tanguay 11-dec-98

import os,string,time
from types import *
import P4Prefs
from P4Prefs import TEST, TEST2, P4Error
import vcs

def getstatusoutput(cmd):
	"""Return (status, output) of executing cmd in a shell."""
	import os
	marker = 'P4.getstatusoutput:'
	if P4Prefs.TEST: print marker, 'Open Pipe to ',cmd
	pipe = vcs.popen(cmd + " 2>&1")
	while 1:
		try:
			if P4Prefs.TEST: print marker,'reading from the pipe...'
			text = pipe.read()
		except IOError:
			import traceback
			traceback.print_exc()
		else:
			break
	sts = pipe.close()
	if sts == None: sts = 0
	if text[-1:] == '\n': text = text[:-1]
	if P4Prefs.TEST: print marker,'result <%d,%s>' % (sts,text[:132])
	return sts, text

#--- class P4Iter -------------------
# usage:
#	wDir = 'xyz'; wFile = 'abc'; label = 'LABEL'
#	for line in P4Iter('-d %s files %s@%s' % (wDir,wFile,label)):
#	   print line

class P4Iter:
	def __init__(self,cmd,rweRoot="",pweRoot="",pweName=""):
		self.curLine=0
		if not rweRoot: rweRoot=vcs.repositoryRoot
		if not pweRoot: 
			pweRoot=vcs.privateRoot
			if not pweName: 
				try:
					pweName=vcs.workingEnvName
				except:
					pass
		theClient=P4Prefs.GetClient(pweRoot,pweName)
		if P4Prefs.TEST: print "P4.P4Iter(",cmd," RWE=",rweRoot," PWE=",pweRoot," Client=",theClient,")"
		#if not theClient: print "ERROR -- cannot obtain client name"
		#	return
		xcmd = "cd %s ; p4 " % pweRoot
		if rweRoot:
			xcmd = xcmd + "-p %s " % rweRoot
		if theClient:
			xcmd = xcmd + "-c %s " % theClient
		# with getstatusoutput() the 2>&1 is not necessary...
		xcmd = xcmd + cmd
		#xcmd = 'D:\\Apps\\Perforce\\p4.exe ' + cmd
##         if TEST:
##             print 'P4Iter("%s")' % cmd
##             print 'cmd executed is "%s"' % xcmd
		try:
			#print '__init__: %f' % time.time()
			rc, self.lines = getstatusoutput(xcmd)
			if rc:
				#if P4Prefs.TEST:
				#	print 'P4Iter("%s") error code %d' % (cmd,rc)
				## ALWAYS print perforce error msg.
				print 'P4Iter("%s") error code %d' % (cmd,rc)
				raise P4Error
			# read all the lines for later processing...
			self.lines = string.split(self.lines,'\n')
		except IOError:
			import traceback
			print 'P4Iter.__init__() error:'
			traceback.print_exc()
			pass
	def __getitem__(self,i):
		#print '__getitem__ for index %d: %s' % (i,self.lines[i])
		return self.lines[i]
	def getoutput(self):
		return string.join(self.lines,'\n')
	def readline(self):
		try:
			result = self.lines[self.curLine]
			self.curLine = self.curLine + 1
			return result
		except:
			return None
	def unreadline(self):
		if self.curLine > 0:
			self.curLine = self.curLine - 1


# ---- utility functions -----------------------------

# detect: implements (similar to) detect: method, like in Smalltalk.
def detect(aString,aList):
	"""
	Detects if one of the strings in aList is in aString.
	"""
	for ss in aList:
		if string.find(aString,ss) > -1:
			return 1
	return 0

def labels():
	"""
	Get the list of all the labels in the depot.
	"""
	marker = 'P4.labels:'
	if P4Prefs.TEST: print marker

	aList = [] # results.

	for line in P4Iter('labels'):
		# Read a line and extract the label (the second field).
		fields = string.split(line)
		try:
			aList.append(fields[1])
		except IndexError:
			if P4Prefs.TEST: print marker,'index error at line "%s"' % line

	return aList

def revisionForLabel(repFile,label):
	"""
	Get the revision number of a repository file for a label.
	Return value is a number.
	"""
	marker = 'P4.revisionForLabel:'
	if P4Prefs.TEST: print marker,repFile,label

	# List the file from the depot (output is on a single line).
	p4 = P4Iter('files %s@%s' % (repFile,label))
	line = p4[0]
	if P4Prefs.TEST: print marker,'line',line

	if detect(line,[' not in label.','is not under','Invalid changelist']):
		if P4Prefs.TEST: print marker,'(error)',line
		return -1

	fields = string.split(line)
	if P4Prefs.TEST: print marker,'fields',fields

	fullFileName = fields[0]
	fields = string.split(fullFileName,'#')
	if len(fields) != 2:
		if P4Prefs.TEST: print marker,'(new error):',line
		return -1

	if P4Prefs.TEST: print marker,fullFileName,'fields[1] <%s>' % fields[1]
	if P4Prefs.TEST: print marker,'atoi(%s) is %d' % (fields[1],string.atoi(fields[1]))

	return string.atoi(fields[1])


def GetRoot(pweRoot, sswe, relativePath, workingDir=""):
	"""find out below which workspace the workingFile is located"""
	starting=1
	for curRoot in [pweRoot] + string.split(sswe):
		# try to minimize number of fstat calls
		if starting and workingDir and workingDir[:len(curRoot)] != curRoot:
			continue
		starting=0
		thePath=vcs.joinpath(curRoot,relativePath)
		st=vcs.get_stat(thePath)
		if vcs.SYSTEM == vcs.WINDOWS:
			if vcs.test_exists(st):
				return curRoot
		else:
			if vcs.test_exists(st) and not os.path.islink(thePath):
				return curRoot
	return pweRoot

def dirstat(rweRoot, pweRoot, relativeDir):
	"""return a statDict dictionary indexed by all the files in the directory;
	pweRoot is necessary to calculate the correct client.
	for SSWE/PWE setup, we might get duplicate entries for some files, but 
	right now we don't care."""
	# need to quote the star to have it resolved by perforce and not the shell
	# this is necessary to get "hidden files" (starting with a dot) as well
	if not relativeDir: relativeDir="."
	P4obj = P4Iter('-d %s fstat %s/\*' % (pweRoot, relativeDir), rweRoot, pweRoot)
	statDict = {}
	curFile = ""
	curDict = {}
	# parse the output
	for line in P4obj:
		if P4Prefs.TEST2: print marker,line
		# Lines from fstat can have 2,3,4 tokens.
		# The first one should be '...'
		# The second one can be '...' when locks are set.
		tokens = string.split(line)
		if len(tokens)==0 or tokens[0] != '...':
			# some file is not under Perforce control, or an empty line:
			# this always denotes the end of a record about one file.
			if curFile:
				# there was a previous record: save it to the dict
				statDict[curFile] = curDict
				curDict = {}
				curFile = ""
			continue
		# we are inside some information about a file.
		if tokens[1] == '...': index = 2
		else:                  index = 1
		field = tokens[index]
		# The field can be a boolean, in which case
		# there is an implicit value of 1.
		try:
			value = tokens[index+1]
		except IndexError:
			value = '1'
		if field == "clientFile":
			(dummy, curFile) = os.path.split(value)
		else:
			curDict[field] = value
		# valid fields:
		# clientFile, depotFile
		# headAction, headChange, headRev, headType, headTime
		# haveRev, action, change, ourLock
		# otherOpen, otherOpen0, otherAction0, otherOpen1, otherAction1, ...
		# otherLock
	if curFile:
		statDict[curFile] = curDict
	return statDict

_fstatCache = {}
def fstat(rweRoot, pweRoot, repFile, field=""):
	"""
	Get the value of 'field' for the repository file repFile.
	Everything is returned as a string.
	"""
	marker = 'P4.fstat:'
	global _fstatCache
	(dir, file) = os.path.split(repFile)
	# since we are looking at the client view of files, fstat may return
	# some different fields for the same file on different clients / workspaces.
	realDir = vcs.joinpath(pweRoot, dir)
	try:
		(statDict, statTime) = _fstatCache[realDir]
	except:
		statTime = - P4Prefs.StatCacheTimeout
	curTime = time.time()
	if curTime - statTime >= P4Prefs.StatCacheTimeout:
		# the cache is not up-to-date
		statDict = dirstat(rweRoot, pweRoot, dir)
		curTime = time.time()
		_fstatCache[realDir] = (statDict, curTime)
	try:
		curDict = statDict[file]
	except:
		curDict = {}
	if field:
		try:
			return curDict[field]
		except:
			return null
	else:
		return curDict


def InvalidateLockersCache(absFile=""):
	global _fstatCache
	if not absFile:
		_fstatCache = {}
	else:
		(dir,file)=os.path.split(absFile)
		try:
			del _fstatCache[dir]
		except:
			pass

def LockerString(rweRoot, pweRoot, repFile, ourUser):
	curDict = fstat(rweRoot, pweRoot, repFile)
	if not curDict:
		return "--no-mapping--"
	if P4Prefs.TEST2: print "LockerString: ",curDict
	result = ""
	actionDict={"add":"A", "delete":"D", "edit":"E"}
	ourLock=" "
	try:
		p4action=curDict["action"]
		ourLock = ourLock+ourUser+":"+p4action
		theAction=actionDict[p4action]
	except:
		theAction=""
	if curDict.has_key("ourLock"):
		theAction=theAction+"L"
		ourLock=ourLock+"(lock)"
	haveRev="0"
	headRev="0"
	theSync=""
	try:
		headRev=curDict["headRev"]
		haveRev=curDict["haveRev"]
		if headRev != haveRev:
			theSync = "!"
		elif not theAction:
			theSync = "="
	except:
		# file is either not on client or freshly added; 
		if not theAction:
			theSync = "?"
	# other people editing or locking the file
	otherLock=""
	try:
		if curDict.has_key("otherLock"):
			otherLock=otherLock + " (otherLock)"
		for curOpen in range(string.atoi(curDict["otherOpen"])):
			curField="otherOpen%i" % curOpen
			#otherUser=string.replace(curDict[curField], '@', '(a)')
			otherUser=string.split(curDict[curField],'@')[0]
			otherAction=curDict["otherAction%i" % curOpen]
			otherLock = otherLock + " "+otherUser+":"+otherAction
	except:
		pass
	# filetype of HEAD
	headType=" "
	if P4Prefs.LockersShowFiletype:
		try:
			headType="<"+curDict["headType"]+">"
		except:
			headType="<?>"
	# branch according to the client's view
	theBranch=""
	if P4Prefs.LockersShowBranch:
		try:
			theBranch=curDict["depotFile"][:-len(repFile)]
		except:
			pass
	if P4Prefs.LockersShowVersion:
		lockerString = theAction + theSync + haveRev + "/" + headRev
	else:
		lockerString = ""
	lockerString = lockerString + headType + theBranch + ourLock + otherLock
	return lockerString

if __name__ == "__main__":
	vcs.repositoryRoot = "perforce:1666"
	vcs.privateRoot = "/sapmnt/home1/d000723/WS/BIN/src"
	print 'try first line of "p4 info"'
	x = P4Iter('info')
	print x[0]
	repFile = '//depot/sniff_we/sswe/COMPLEX/complex/main.C'

# eof P4.py
 
